/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package miner

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"time"

	commontypes "hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/consensus/types"
	"hundsun.com/hsl/hschain/protos"

	protoscommon "hundsun.com/hsl/hschain/protos/common"

	"go.uber.org/atomic"
	"hundsun.com/hsl/hschain/common/keystore"
	"hundsun.com/hsl/hschain/common/log/logging"
	pbledger "hundsun.com/hsl/hschain/protos/ledger"
)

var emptyBlock = &pbledger.Block{}

// Producer 负责出块
type Producer struct {
	module *RaftMiner
	ctx    context.Context

	latestCreatedBlock   atomic.Value
	latestBroadCastBlock atomic.Value
	isProducing          *atomic.Bool
}

func (p *Producer) init(minerAccount keystore.Account, producingInterval, pendingTxPoolRetryInterval, checkBlockExecResultRetryInterval int) error {
	var err error
	producerAdapterConf := &types.ProducerConfig{
		ModuleLog:                            p.module.Log,
		MinerAccount:                         minerAccount,
		ProducingInterval:                    producingInterval,
		MinPendingTxFetchInterval:            pendingTxPoolRetryInterval,
		MinCheckBlockExecResultFetchInterval: checkBlockExecResultRetryInterval,
	}

	shouldProceed := func() bool {
		if !p.isProducing.Load() {
			return false
		}
		if !p.module.cm.IsLeader() {
			p.module.baseclient.SetBlockSynchronized(false)
			return false
		}
		// 刚刚切换成为leader，必须使账本同步
		if p.module.cm.IsLeader() && !p.module.baseclient.BlockSynchronized() {
			for !p.module.IsBlockSynchronized() {
				time.Sleep(50)
			}
		}
		return true
	}

	beforeBlockCreatedFunc := func() (lastBlockNumber uint64, lastBlockHash protos.Hash) {
		latestCreatedBlock := p.latestCreatedBlock.Load().(*pbledger.Block)
		if latestCreatedBlock.Header == emptyBlock.Header {
			latestBlockCheckedMsg := p.module.baseclient.GetLastBlockCheckedMessage()
			lastBlockNumber = latestBlockCheckedMsg.BlockNumber
			lastBlockHash = latestBlockCheckedMsg.BlockHash
			p.module.Log.Tracef("try to produce with prev block[number='%d',lastBlockHash=%s] in BlockCheckedMessage", lastBlockNumber, hex.EncodeToString(lastBlockHash))
		} else {
			lastBlockNumber = latestCreatedBlock.Header.Number
			lastBlockHash = latestCreatedBlock.Hash()
			p.module.Log.Tracef("try to produce with  prev block[number='%d',lastBlockHash=%s] in BlocksBuffer", lastBlockNumber, hex.EncodeToString(lastBlockHash))
		}
		return
	}

	afterBlockCreatedFunc := func(newBlock *pbledger.Block) {
		newBlockHash := newBlock.Hash()
		blkLog := p.module.Log.WithFields(map[string]interface{}{
			logging.LogFieldBlockHash:   newBlockHash.GetShortString(),
			logging.LogFieldBlockNumber: newBlock.Header.Number,
		})

		//raft模式一旦块号出现错误，没有回退机制，因此必须保证上一个块请求到序列号后再发送下一个块
		for {
			if !shouldProceed() {
				return
			}
			latestCreatedBlock := p.latestCreatedBlock.Load().(*pbledger.Block)
			if latestCreatedBlock.Header == emptyBlock.Header {
				break
			}
			latestSequencedBlock := p.latestBroadCastBlock.Load().(*pbledger.Block)
			if latestSequencedBlock.Header.Number == latestCreatedBlock.Header.Number {
				break
			}
			blkLog.Debug("waiting for sequence of previous block...")
			time.Sleep(50)

		}

		// 请求提交，不通过则放弃
		commitNum := p.module.baseclient.GetLastBlockCheckedMessage().BlockNumber
		if p.module.cm.IsLeader() && p.module.cm.Commit(commitNum) {
			p.latestCreatedBlock.Store(newBlock)
			// TODO send方法考虑中断
			//p.module.BlockSequenceClient.Send(newBlock)
			p.module.baseclient.WriteBlock(newBlock)
			blkLog.Infof("Create new block: %d, write to blockchain.\n", newBlock.Header.Number)
		}
	}

	err = p.module.baseclient.InitProducer(producerAdapterConf, shouldProceed, beforeBlockCreatedFunc, afterBlockCreatedFunc)
	if err != nil {
		return err
	}

	p.module.baseclient.SubReplyAddBlockFunc(func(block *pbledger.Block) error {
		bLog := p.module.Log.WithField(logging.LogFieldBlockNumber, block.Header.Number)

		latestBlk := p.latestCreatedBlock.Load().(*pbledger.Block)
		if latestBlk.Header == emptyBlock.Header {
			return commontypes.ErrEmpty
		}

		if !latestBlk.Hash().Equals(block.Hash()) {
			return commontypes.ErrBlockHashNoMatch
		}

		p.latestBroadCastBlock.Store(block)
		err = p.module.baseclient.BroadCastBlock(block, 5)
		if err != nil {
			bLog.Error("broadcast block failed, err: %s\n", err.Error())
			return err
		}
		return nil
	})
	return nil
}

//Run 出块
func (p *Producer) Run() {
	p.module.Log.Infof("start producer run...")
	defer p.module.Log.Info("exit producer run...")

	for {
		select {
		case <-time.After(time.Second * 6):
			p.produceBlocks()
		case <-p.ctx.Done():
			return
		}
	}
}

func (p *Producer) produceBlocks() {
	p.module.Log.Info("produceBlocks entry...")
	defer p.module.Log.Info("produceBlocks exit...")
	//出块前准备工作
	p.latestCreatedBlock.Store(emptyBlock)
	p.latestBroadCastBlock.Store(emptyBlock)
	var latestBlockCheckedMsg *protoscommon.BlockCheckedMessage
	for latestBlockCheckedMsg == nil {
		if !p.isProducing.Load() {
			return
		}
		//等几秒，尽可能让miner切换时，旧miner发出的块能被序列号中心保存
		time.Sleep(time.Millisecond * 6)
		latestBlockCheckedMsg = p.GetLatestBlockCheckedMessageIfSynchronized()
	}

	p.module.baseclient.ProduceBlocks()
}

// GetLatestBlockCheckedMessageIfSynchronized 如果已校验的区块的序列号和序列号中心的序列号一致，则返回最新的BlockCheckedMessage；否则，返回nil
func (p *Producer) GetLatestBlockCheckedMessageIfSynchronized() *protoscommon.BlockCheckedMessage {
	//chkMsgBytes, err := Client.GetLatestMessage(message.TopicCheckedBlock)
	lastCheckedBlockBytes, err := p.module.baseclient.RequestTopicInfo(commontypes.EventQueryCheckedBlockTopic)
	if err != nil {
		p.module.Log.Errorf("failed to get latest BlockCheckedMessage, err: %s\n", err.Error())
		return nil
	}

	var checkedMsg = &protoscommon.BlockCheckedMessage{}
	err = json.Unmarshal(lastCheckedBlockBytes, checkedMsg)
	if err != nil {
		p.module.Log.Errorf("Unmarshal ")
	}

	//blockSequence, err := p.module.BlockSequenceClient.GetMaxBlockSequence()
	maxExecutedBlockNumber, err := p.module.baseclient.RequestMaxExecutedBlockNumber()
	if err != nil {
		p.module.Log.Warn("failed to get max block Sequence: %s", err.Error())
		return nil
	}

	if checkedMsg.BlockNumber == maxExecutedBlockNumber {
		p.module.Log.Infof("sequence of  BlockCheckedMessage '%d' is equal to maxBlockSequence in seqCenter", maxExecutedBlockNumber)
		return checkedMsg
	}
	return nil
}
